package ai;

import de.dqi.pong.Formatter;
import de.dqi.pong.engine.AI;
import de.dqi.pong.engine.TickNotification;
import static de.dqi.pong.engine.Direction.*;

/**
 * 
 * <hr><dl><dt>
 * Summary: </dt><br><dd>
 *  
 * </dd><br><dt>
 * Author: </dt><br><dd>
 *  Vincent
 * </dd></dl>
 */
public class Predicter
		extends AI {
	
	private double				xMin;
	private double				yMin;
	private double				width;
	private double				height;
	private double				xMax;
	private double				yMax;
	
	private double				xStart;
	private double				yStart;
	private double				slopeStart;
	private double				yBase;
	private double				distance;
	private double				xAim;
	private double				yAim;
	private double				slopeAim;
	
	private double				deltaX;
	private double				deltaY;
	private double				delta;
	
	private double				xSelf;
	private long				lastWait;
	private boolean				ballOut			= true;
	private boolean				gameRunning;
	private TickNotification	prev;
	private TickNotification	now;
	
	private final int			waitIntervall	= 5;
	private final double		acceleration	= 1 / 1500;
	
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		now = tick;
		setup();
		gameRunning = true;
		ballOut = true;
		
		while (gameRunning) {
			if (ballOut) {
				respawn();
			}
			if (xAim == xSelf) {
//				System.err.println("incoming");
				defend();
			} else {
//				System.err.println("leaving");
				prepare();
			}
		}
	}
	
	
	private void setup() {
		lastWait = System.currentTimeMillis();
		prev = tick;
		
//		xMin = init.ballRadius;
		yMin = init.ballRadius;
		
//		width = init.fieldWidth - 2 * init.ballRadius;
		height = init.fieldHeight - 2 * init.ballRadius;
		
//		xMax = xMin + width;
		yMax = yMin + height;
		
		xMin = 0;
//		yMin = 0;
//		
		width = init.fieldWidth;
//		height = init.fieldHeight;
//		
		xMax = width;
//		yMax = height;
		
		if (isLeft) {
			xSelf = xMin;
		} else {
			xSelf = xMax;
		}
		
		now = tick;
	}
	
	
	private void defend() {
//		System.out.println("start");
		//WIP: going for most random reflection.
		double variance = (init.batSpan * 2 / 3) - .25;
		if (slopeAim > 0) {
			if (isLeft) {
				yAim -= variance;
			} else {
				yAim += variance;
			}
		} else {
			if (isLeft) {
				yAim += variance;
			} else {
				yAim -= variance;
			}
		}
		
		boolean incoming = true;
		while (incoming) {
			
			if (inBounds(now.posBallX, xMin, xMax)) {
				if (isLeft) {
					if (deltaX > 0) {
						incoming = false;
					} else if (now.posBatLeft > yAim + .5) {
						direction = Down;
					} else if (now.posBatLeft < yAim - .5) {
						direction = Up;
					} else {
						direction = Hold;
					}
				} else {
					if (deltaX < 0) {
						incoming = false;
					} else if (now.posBatRight > yAim + .5) {
						direction = Down;
					} else if (now.posBatRight < yAim - .5) {
						direction = Up;
					} else {
						direction = Hold;
					}
					
				}
				waitTicks(1);
			} else {
				incoming = false;
				checkScore();
			}
//			System.err.println(Formatter.format(now.posBallY, 2, 2) + " | "
//					+ Formatter.format(ballPosAt(now.posBallX)));
		}
		if (!ballOut) {
			traceBall();
		}
//		System.out.println("end");
	}
	
	private void prepare() {
		boolean incoming = false;
		double aim = height / 2;
		while (!incoming) {
			if (inBounds(now.posBallX, xMin, xMax)) {
				if (isLeft) {
					aim = (height / 2 + now.posBatRight) / 2;
					if (deltaX < 0) {
						incoming = true;
//						System.err.println("hit@ " + prev.posBallX + "|" + prev.posBallY);
					} else if (now.posBatLeft > aim + .5) {
						direction = Down;
					} else if (now.posBatLeft < aim - .5) {
						direction = Up;
					} else {
						direction = Hold;
					}
				} else {
					aim = (height / 2 + now.posBatLeft) / 2;
					if (deltaX > 0) {
						incoming = true;
//						System.err.println("hit@ " + prev.posBallX + "|" + prev.posBallY);
					} else if (now.posBatRight > aim + .5) {
						direction = Down;
					} else if (now.posBatRight < aim - .5) {
						direction = Up;
					} else {
						direction = Hold;
					}
				}
				waitTicks(1);
			} else {
				incoming = true;
				checkScore();
			}
			
		}
		if (!ballOut) {
			traceBall();
		}
	}
	
	
	private void checkScore() {
//		System.err.println("out@ " + now.posBallX + "|" + now.posBallY);
		
		ballOut = true;
		direction = Hold;
		if (now.scoreLeft >= 10 || now.scoreRight >= 10) {
			gameRunning = false;
		}
	}
	
	
	private void respawn() {
		boolean spawning = true;
		while (spawning) {
			waitTicks(1);
			if (inBounds(now.posBallX, xMin, xMax) && inBounds(now.posBallY, yMin, yMax)) {
				spawning = false;
			}
		}
		ballOut = false;
		distance = 1;
		waitTicks(1);
		traceBall();
	}
	
	
	private int ticksToTravel(double xDiff) {
		double accX = acceleration * deltaX / delta;
		double ticks = -1 * Math.abs(deltaX) * accX;
		ticks += Math.sqrt(ticks * ticks + 2 * xDiff / accX);
		
		return (int) ticks;
	}
	
	
//	private void traceBall() {
//		boolean tracing = true;
//		while (tracing) {
//			if (inBounds(prev.posBallY, yMin + distance, yMax - distance)
//					|| inBounds(now.posBallY, yMin + distance, yMax - distance)) {
//				tracing = false;
//			} else if (isNear(delta, distance)) {
//				tracing = false;
//			} else {
//				waitTicks(1);
//			}
//		}
//		slopeStart = deltaY / deltaX;
//		xStart = prev.posBallX;
//		yStart = prev.posBallY;
//		yBase = yStart;
//		yBase = ballPosAt(-xStart);
//		if (deltaX < 0) {
//			xAim = xMin;
//		} else {
//			xAim = xMax;
//		}
//		yAim = ballPosAt(xAim);
//		double absPos = yBase + slopeStart * xAim;
//		int bounces = (int) (absPos / height);
//		if (absPos < 0) {
//			bounces = 1 - bounces;
//		}
//		slopeAim = slopeStart;
//		if (bounces % 2 == 1) {
//			slopeAim *= -1;
//		}
////		System.err.println("nxt@" + bounces + " " + slopeAim + " " + xAim + "|" + yAim);
//	}
//	
//	private double ballPosAt(double xPos) {
//		double yTemp = yBase + slopeStart * xPos;
//		yTemp = Math.abs(yTemp);
//		yTemp %= 2 * height;
//		if (yTemp > height) {
//			yTemp = 2 * height - yTemp;
//		}
//		return yTemp;
//	}
	
	private void traceBall() {
		boolean tracing = true;
		while (tracing) {
			if (inBounds(prev.posBallY, yMin + distance, yMax - distance)
					|| inBounds(now.posBallY, yMin + distance, yMax - distance)) {
				tracing = false;
			} else if (isNear(delta, distance)) {
				tracing = false;
			} else {
				waitTicks(1);
			}
		}
		slopeStart = deltaY / deltaX;
		xStart = prev.posBallX;
		yStart = prev.posBallY - init.ballRadius;
		yBase = yStart;
		yBase = ballPosAt(-xStart) - init.ballRadius;//
		if (deltaX < 0) {
			xAim = xMin;
		} else {
			xAim = xMax;
		}
		yAim = ballPosAt(xAim);
		double absPos = yBase + slopeStart * xAim;
		int bounces = (int) (absPos / height);
		if (bounces < 0) {
			bounces = 1 - bounces;
		}
		slopeAim = slopeStart;
		if (bounces % 2 == 1) {
			slopeAim *= -1;
		}
//		System.err.println("nxt@" + bounces + " " + slopeAim + " " + xAim + "|" + yAim);
	}
	
	private double ballPosAt(double xPos) {
		double yTemp = yBase + slopeStart * xPos;
		yTemp = Math.abs(yTemp);
		yTemp %= 2 * height;
		if (yTemp > height) {
			yTemp = 2 * height - yTemp;
		}
		return yTemp + init.ballRadius;
	}
	
	
	private void waitTicks(int count) {
		TickNotification current = now;
		while (count > 0) {
			if (lastWait + 20 < System.currentTimeMillis()) {
				System.err.println("Predicter");
			}
			
			try {
				Thread.sleep(waitIntervall);
			} catch (InterruptedException e) {
			}
			
			if (tick != current) {
				count--;
				prev = current;
				current = tick;
				now = current;
				
				deltaX = now.posBallX - prev.posBallX;
				deltaY = now.posBallY - prev.posBallY;
				delta = Math.hypot(deltaX, deltaY);
			}
			
			lastWait = System.currentTimeMillis();
		}
	}
	
	
	private boolean isNear(double value, double aim) {
		if (aim - .05 < value && value < aim + .05) { return true; }
		return false;
	}
	
	private boolean inBounds(double value, double low, double high) {
		return (low <= value && value <= high);
	}
	
	private double ySelf(TickNotification state) {
		if (isLeft) { return state.posBatLeft; }
		return state.posBatRight;
	}
	
	private double yOpponent(TickNotification state) {
		if (isLeft) { return state.posBatRight; }
		return state.posBatLeft;
	}
	
}
